Liberty Basic Beginner Series - Part 6

File I/O

© 2003, Brad Moore

Remembering data between sessions -

I am guessing that nearly everyone has either played video games or has a son or daughter that plays them. If so, then you are familiar with the concept of the high score. It is the high water mark that has been reached by any given player playing the game since it was installed or last reset. It is persistent - meaning it exists between gamming sessions, even when the game machine has been turned off.

So far, all the variables we have dealt with are destroyed as soon as you exit Liberty Basic, much less turn off your computer. In fact no form of variable in Liberty Basic can exist after Liberty Basic has been exited. So you ask "How does this work?" Well, as you might be suspecting, we actually write some data out to the hard disk. Variables are created and maintained in RAM which is temporary, but data written to the hard disk is persistent. These files are physically written on the surface of the hard disk and they are not destroyed by exiting Liberty Basic or turning the power off on your PC.

This is all new ground for us. We will be just scratching the surface here, introducing a subject called File I/O (meaning Input/Output). Liberty Basic has a fairly good set of file I/O commands. We will use four of them. OPEN, CLOSE, INPUT and PRINT. It may surprise you to discover that the trusty INPUT and PRINT statements allow file I/O, but when you think about it, there really is not a lot of difference between printing to the CRT or a disk file. Likewise, should it matter too much what device provides input, either a disk file or a keyboard? Not really.

The commands (PRINT and INPUT) have a few additional arguments to allow them to interoperate with the file system. Before we can use either of these commands, we must actually have a file open for access. This is the job of the OPEN statement. It will open a disk file (among other things) so that we can write to it, read from it, or both. We quickly reference the help file for details on the syntax (Note: I have edited it for simplicity):

OPEN string FOR purpose AS #handle 

Description:

This statement has many functions.  It can be used to open disk files, DLLs, or to open windows of several kinds.

Using OPEN with Disk files:

A typical OPEN used in disk I/O looks like this:

  OPEN "\autoexec.bat" for input as #readfile

This example illustrates how we would open the autoexec.bat file for reading.  As you can see, string in this case is "\autoexec.bat", purpose is input, and #handle is readfile.

  string 	- this must be a valid pathname.  If a path is not included, the program's DefaultDir$ is assumed to be the location of the file.  Partial and relative paths may also be used.

  purpose 	- must be input, output, random, or binary

  #handle 	- use a unique descriptive word, but must start with a #.  This special handle is used to identify the open file in later program statements.

Well, that seems like quite a bit, and in fact it potentially is quite a bit. We will not be developing all the possible attributes of the open statement and the file I/O system. Whole newsletter articles have been written on the subject, and I would encourage you to seek them out and read them. Likewise, the help file suggest that you read more about file operations in the help file.

For our purposes, let us just say we will be opening a file that exists in the same directory as the program is executing. In this scenario we will not need to be concerned with the path portion of the pathname of the file we want to open, the path is presumed to be the directory we are working in, which is also stored in the variable DefaultDir$. If our file is called "highscore.dat" and it is in the directory where we are working, then we have all we need to locate the file. This satisfies the pathname argument.

What we need to determine is the mode to open the file. There are basically five modes to open the file in. They are input, output, append, random and binary. We will not be considering random or binary for this article. Here is what the help file says about the file open methods:

INPUT - files opened for input may be read from, but not written to.  If the file does not exist, then attempting to open it for input will generate an error.  Check a file's existence with the FILES statement.

OUTPUT - files opened for output may be written to, but not read from.  If a file opened for output already exists, its contents will be erased, so use this with care.    If the file does not exist, it will be created.

APPEND - files opened for append may be written to, but not read from.  Data will be appended to the end of the existing file.  If the file does not exist, it will be created.

If you look closely you will notice that there is not a method where by we can both read data from the file and write data out to the file. This is by design. If we want to get the contents of the file we need to open the file in INPUT mode. The caveat here is that if the file does not exist, then an error will occur and your program will halt. There is no way in Liberty Basic 3.x to capture the error condition and act on it preventing the premature termination of the program (LB4 does have this feature). Because we can never be sure that the file is called what we expect, or is located where we expect, we can just open the file for INPUT without verifying that the file first is there. As the help file suggests, this can be done with the FILES function. This is a fairly complex function and one I do not intend to cover at this time.

There is an alternate, an old trick that Liberty Basic programmers have been using for sometime. You will notice that both the OPEN and APPEND modes will create the file if it does not exist. This means that if we first open the file with one of these modes and the file is not there, it will be created, thus guaranteeing that the file is where we expect it. But which mode? Can you guess? Consider the unique feature of both of the modes which both write data out to the file. OPEN will erase the contents of any file opened and allow you to begin with a fresh, clean file. Append will leave the data in tact, and allow you to begin writing data on the end of the file. The original contents are not disturbed.

The mode we want is APPEND, because we do not want to damage the data in the file, should it already exist. If we open it in APPEND mode, the CLOSE the file, then open it in INPUT mode (allowing reading from the file - AKA input into the program variables), then we can be sure that the OPEN for INPUT will not fail. Understand why? If the file was already there, then we have not harmed it with the OPEN for APPEND, if the file was not there then we have created an empty file.

Lets talk about the handle argument. This one tripped me up for a while, until I understood just how simple it is. The handle is used to associate the file we opened with later file I/O commands. It binds them together. But this is even a complex view. You can consider the handle a special variable that you get to create, like a string it has an indicator (the # sign). It will be used in subsequent commands. It can be basically any string of characters you want to use. It must be preceded by a pound sign ("#"). Some people like to use numbers for files, like #1 or #10, other people like descriptive names such as #dat or #myfile.

Once you have opened a file you will eventually want to close it. You need to close all open files before your program ends or you will get an error.

You also must close the file before you can open it again. This might seem elementary, but I have puzzled over code for hours trying to figure out what I did wrong only to determine that I did not close the file before opening it in another mode. Close files with the CLOSE command. The syntax is simple:

Close #handle

I want to introduce you to a little code that demonstrates many of the techniques we have covered and some additional ones I am about to explain, including the actual writing to and reading from a file, as well as more on the OUTPUT and APPEND modes.

'open the high scores file with append 
'if it exists it will not harm it, if it does not exist
'it will create it so that the open with input does not fail
open "highscore.dat" for append as #dat
close #dat
'now we can open it for input (read)
open "highscore.dat" for input as #dat
'check EOF to see if there are any records
if eof(#dat) < 0 then
   print "This file does not have a high score"
else
   'there is something in the file.  It should be a number
   input #dat, text$
   highscore = val(text$)
   print "High score is: ";highscore
end if
close #dat

input "Enter the new high score: ";a$
'this time we will open highscore.dat with output to destroy the old one
open "highscore.dat" for output as #dat
print #dat, a$
close #dat

This is the a standalone code model that we will be basing our high score code on in our game that we are developing. Copy it to your Liberty Basic IDE, save it (that is critical) and run it. In the code above we use the technique we discussed earlier of opening the highscore.dat file in APPEND mode to insure it exists, then we open it in INPUT mode so that we can read the data from it.

If the file was freshly created by the opening in APPEND mode, then it will have no data in it, and it will have a length of zero. Length is important in many cases, because reading beyond the end of a file will cause an error that will prematurely halt the program. Don't worry, we can determine both how long a file is, and when we have reached the end of the file while reading from it - more on that later.

Reading from the file is really accomplished using the INPUT command. A quick look at the Liberty Basic help file reveals that the INPUT command is much more than it seems.

INPUT  #handle  "string expression";  variableName

Description:

This command has several possible forms:

  input var

- stop and wait for user to enter data in the program's main window and press the 'Return' key, then assign the data entered to var.

input "enter data"; var  

- display the string "enter data" and then stop and wait for user to enter data in the program's main window and press 'Return', then assign the data entered to var.

input #name, var

- Get the next data item from the open file or device using handle named #handle and assign the data to var.  If no device or file exists that uses the handle named #handle, then return an error.

The first two forms of INPUT we have used. The third version is new. This is the form of the statement used to get data from a file. Data is read in chunks called data items. Data items are separated by commas. The end of a line in the file or the end of the file also designates the end of a data item. Reading the data items from the file is done beginning with the first item, and moving sequentially through the file. We call this form of reading sequential file access. It is important to realize that in this form of file access we must be careful not to read past the end of the file as we read record after record. As I mentioned, reading past the end of the file will cause an error.

I know that was getting pretty technical - especially for our basic purpose - all we want to do is write a single number into the text file and later read it out. Getting back to the INPUT statement. The usage is fairly simple. We specify the handle of an open file and the variable we want the data we read from the file to be "input" into. You can input into a numeric variable, but if you remember my warnings from earlier installments of this series, it is fraught with risk. It is far better to input your data into a string and then use the VAL statement to convert it to a number. This is what we do in the code above.

You are probably wondering about the EOF(#dat) stuff. EOF is a file I/O function that tells you about the status of your serial read operation, specifically if you have arrived at the end of the file. EOF returns a minus one (-1) if you are at the end of the file, but a zero other wise. I use it in the code right off the bat to determine whether there is any data in the file at all. If the file had been newly created as discussed earlier then the EOF(#dat) would instantly return a negative value (minus one) since there was no data. In this case I skip reading from the file. Otherwise I execute a single INPUT statement against the open file and them close the file. The contents of the file are retrieved in the variable text$ which is converted to a number using the VAL function. I display the value read.

Once the file is closed I query the user for a new data element - hopefully a number. This is then written back to the very some file. Lets take a look at how.

Once again the file is opened before any file I/O can be done. This time we want to replace the contents of the file with all new data (my simulated top score in this demo). So I open the file in OUTPUT mode which destroys the contents of the file.

Now the PRINT statement is used to print (or output) the data to the file. The data is the value we retrieved form the user. The PRINT statement has the same arguments as the INPUT statement. Here is the syntax:

Print #handle, variable

The #handle is a file handle of a previously opened file. It must be opened in OUTPUT or APPEND modes.

"variable" is a string or numeric variable containing the data we want to write to the file.

Run the code snippet again and play with it. See if you understand the component parts. There is much more to file I/O. We have just scratched the surface, but it is enough to get us going in our game. Let me show you where I integrated this code into the program.

Adding High Score Processing to Starquest

We begin adding high score processing to Starquest by first opening and accessing the high score file (highscore.dat) at the very beginning of the game. This happens even before first label. The code will look vary familiar, as it is based on the concepts we discussed above.

'get the high score from the highscore.dat file (open with append)
'if file exists it will not harm it, if it does not exist
'it will create it so that the open with input later does not fail
open "highscore.dat" for append as #dat
close #dat
'now we can open it for input (read)
open "highscore.dat" for input as #dat
'check EOF to see if there are any records
if eof(#dat) < 0 then
   print "No high score found."
   highscore = 0
else
   'there is something in the file.  It should be a number
   input #dat, text$
   highscore = val(text$)
end if
close #dat

Once the code above is executed, then the variable highscore will be populated with either a 0, or the value that was in the highscore.dat file. Now we simply keep this value in our pocket until the game is over, and then check to see if the score from our current game is greater than the high score or not. I embedded this logic into the [winner] subroutine we enhanced earlier. Immediately after displaying the players score we do a comparison against the high score using an IF-THEN-END IF statement. If our score is greater than high the earlier retrieved high score, then we inform the user they have the new high score and write the new high score out to the highscore.dat file. Here is the code:

print "   *** Your final score for the whole game is: ";score;" ***"
if score > highscore then
   print "            >>> This is a new High Score <<<"
   'open the highscore file and write the new high score out
   open "highscore.dat" for output as #dat
   print #dat, str$(score)
   close #dat
   highscore = score
end if
print
print "========================================================"

That completes high score processing. Next, lets examine adding levels to the program.


Home

Tip Corner

Plot 3d

Memory Mapped File

Random File Selector

Notes for Beginners

No SoundBlaster Board

PrintInstalled Fonts

Essential Libby

API Drive Info

Begginer Series 6

Newsletter help

Index